package com.basker.pisces.data.execute;

import java.sql.ResultSetMetaData;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.ResultSetExtractor;
import org.springframework.util.Assert;

import com.basker.pisces.core.RowContract;
import com.basker.pisces.core.meta.IDataField;
import com.basker.pisces.core.meta.IField;
import com.basker.pisces.core.meta.ReferenceField;
import com.basker.pisces.core.meta.row.RowContractMeta;
import com.basker.pisces.orm.data.DataTable;
import com.basker.pisces.orm.data.meta.TableMeta;
import com.basker.pisces.orm.execute.ISqlExecutor;

public class SpringJdbcSqlExecutor implements ISqlExecutor {

    @Autowired
    private DynamicJdbcTemplate jdbcTemplate;

    @Override
    public int[] batchUpdate(String sql, List<Object[]> batchArgs, int[] argTypes) {
        Assert.hasText(sql, "parameter 'sql' is required");

        if (argTypes == null || argTypes.length == 0) {
            return this.jdbcTemplate.batchUpdate(sql, batchArgs);
        } else {
            return this.jdbcTemplate.batchUpdate(sql, batchArgs, argTypes);
        }
    }

    @Override
    public void execute(String sql) {
        Assert.hasText(sql, "parameter 'sql' is required");

        this.jdbcTemplate.execute(sql);
    }

    @Override
    public DataTable queryForDataTable(TableMeta tableMeta, String sql, Object[] args, int[] argTypes) {
        Assert.notNull(tableMeta, "parameter 'tableMeta' is required");
        Assert.hasText(sql, "parameter 'sql' is required");

        ResultSetExtractor<DataTable> rse = resultSet -> {
            DataTable dt = new DataTable(tableMeta);
            dt.addResultSet(resultSet);

            return dt;
        };

        if (argTypes == null || argTypes.length == 0) {
            return this.jdbcTemplate.query(sql, rse, args);
        } else {
            return this.jdbcTemplate.query(sql, args, argTypes, rse);
        }
    }

    @Override
    public List<Map<String, Object>> queryForList(String sql, Object[] args, int[] argTypes) {
        Assert.hasText(sql, "parameter 'sql' is required");

        if (argTypes == null || argTypes.length == 0) {
            return this.jdbcTemplate.queryForList(sql, args);
        } else {
            return this.jdbcTemplate.queryForList(sql, args, argTypes);
        }
    }

    @Override
    public Map<String, Object> queryForMap(String sql, Object[] args, int[] argTypes) {
        Assert.hasText(sql, "parameter 'sql' is required");

        if (argTypes == null || argTypes.length == 0) {
            return this.jdbcTemplate.queryForMap(sql, args);
        } else {
            return this.jdbcTemplate.queryForMap(sql, args, argTypes);
        }
    }

    @Override
    public <TResult> TResult queryForObject(Class<TResult> requiredType, String sql, Object[] args, int[] argTypes) {
        Assert.notNull(requiredType, "parameter 'requiredType' is required");
        Assert.hasText(sql, "parameter 'sql' is required");

        if (argTypes == null || argTypes.length == 0) {
            return this.jdbcTemplate.queryForObject(sql, args, requiredType);
        } else {
            return this.jdbcTemplate.queryForObject(sql, args, argTypes, requiredType);
        }
    }

    @Override
    public List<RowContract> queryForRows(RowContractMeta rowMeta, String sql, Object[] args, int[] argTypes) {
        Assert.notNull(rowMeta, "parameter 'rowMeta' is required");
        Assert.hasText(sql, "parameter 'sql' is required");

        ResultSetExtractor<List<RowContract>> rse = resultSet -> {
            ResultSetMetaData resultMetaData = resultSet.getMetaData();

            int columnCount = resultMetaData.getColumnCount();
            List<IDataField> dataFields = new ArrayList<>(columnCount);

            for (int i = 0; i < columnCount; i++) {
                String colName = resultMetaData.getColumnLabel(i + 1);
                IField field = rowMeta.getField(colName);

                if (field == null) {
                    throw new RuntimeException(colName + " is not found in rowMeta '" + rowMeta.toString() + "'");
                }

                if (field instanceof IDataField) {
                    dataFields.add((IDataField) field);
                } else if (field instanceof ReferenceField) {
                    dataFields.add(((ReferenceField) field).getReferenceIdField());
                } else {
                    throw new RuntimeException(colName + " is not DataField");
                }
            }

            List<RowContract> rows = new ArrayList<RowContract>();

            while (resultSet.next()) {
                RowContract row = (RowContract) rowMeta.createDataObject();

                int index = 0;
                for (IDataField dataField : dataFields) {
                    Object value = resultSet.getObject(index + 1);
                    dataField.setValue(row, value);
                    index++;
                }

                rows.add(row);
            }

            return rows;
        };

        if (argTypes == null || argTypes.length == 0) {
            return this.jdbcTemplate.query(sql, rse, args);
        } else {
            return this.jdbcTemplate.query(sql, args, argTypes, rse);
        }
    }

    @Override
    public int update(String sql, Object[] args, int[] argTypes) {
        Assert.hasText(sql, "parameter 'sql' is required");

        if (argTypes == null || argTypes.length == 0) {
            return this.jdbcTemplate.update(sql, args);
        } else {
            return this.jdbcTemplate.update(sql, args, argTypes);
        }
    }

}
